package mpp;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockBasedHashTable {

	float loadFactor; // Load Factor
	Vector<Bucket> buckets = new Vector<Bucket>(1);
	Lock[] locks;

	AtomicInteger elements_number;
	AtomicLong resizing;
	List<AtomicInteger> lockList;
	
	public LockBasedHashTable(float loadFact, int lockNumber) {
		loadFactor = loadFact;

		buckets.add(new Bucket());

		elements_number = new AtomicInteger(0);
		resizing = new AtomicLong(-1);

		locks = new Lock[lockNumber];
		lockList = new ArrayList<AtomicInteger>(lockNumber);
		
		for (int i = 0; i < lockNumber; i++) {
			locks[i] = new ReentrantLock();
			lockList.add(new AtomicInteger(-1));
		}
		
		 
	}

	public LockBasedHashTable(float loadFact, int bucketSize, int lockNumber) {
		this(loadFact, lockNumber);
		buckets = new Vector<Bucket>(bucketSize);
	}

	public void put(Object item) {
		Node insertedNode = new Node(item);
		int keyHash = item.hashCode() % locks.length;

		try {
			// Lock the lock in the (hash MOD buckets.capacity()) MOD
			// locks.size() place
			locks[keyHash].lock();
		
			
			lockList.get(keyHash).set(ThreadID.get());
			
			// Insert the new Node.
			buckets.get(keyHash % buckets.capacity()).put(insertedNode);

			// Update the total number.
			elements_number.getAndIncrement();
		}

		finally {
			// release the Lock
			try{
			locks[keyHash].unlock();
			
			// Handling with exceptions.
			lockList.get(keyHash).set(-1);
			} catch (IllegalMonitorStateException ex) {
				System.out.println("MyThread: " + ThreadID.get() + " - Releasing lock " + keyHash);
				System.out.print("[");
				for (int i = 0; i<locks.length; i++){
					System.out.print(lockList.get(i).get());
					if (i != locks.length - 1)
						System.out.print(" | ");
				}
				System.out.print("]");
				
				throw ex;
				}
			
		}
		

		// Check for LoadFactor
		if (((float) elements_number.get() / buckets.capacity()) > loadFactor) {
			resizeBuckets();
 
		}
	}

	public Object get(int key) {
		// if resizing, halt.
		while (!resizing.compareAndSet(-1, -1)) {
		}
		;

		return buckets.get(key % buckets.capacity()).get(key);
	}

	private void resizeBuckets() {

		while (!resizing.compareAndSet(-1, ThreadID.get())) {};
				
		try {
			for (int i = 0; i<locks.length; i++){
				locks[i].lock();
				lockList.get(i).set(ThreadID.get());
			}
			if (!(((float) elements_number.get() / buckets.capacity()) > loadFactor)) {
				return;
			}
			
			// New Bucket Vector
			Vector<Bucket> newBuckets = new Vector<Bucket>(
					buckets.capacity() * 2);
			for (int i = 0; i < newBuckets.capacity(); i++) {
				newBuckets.add(new Bucket());
			}

			
			for (Bucket bucket : buckets) {
				for (Node node = bucket.head; node != null; node = node.next) {
					int hash = node.item.hashCode();
					newBuckets.get(hash % newBuckets.capacity()).put(node);
				}
			}
			
			buckets = newBuckets;
		} finally {
			try{
				for (int i = 0; i<locks.length; i++){
					locks[i].unlock();
					lockList.get(i).set(-1);
				}
				resizing.compareAndSet(ThreadID.get(), -1 );
			} catch (IllegalMonitorStateException ex) {
				System.out.print("[");
				for (int i = 0; i<locks.length; i++){
					System.out.print(lockList.get(i).get());
					if (i != locks.length - 1)
						System.out.print(" | ");
				}
				System.out.print("]");
				
				throw ex;
				
			}
		}

	}

	private class Bucket {
		Node head;

		public Bucket() {
			head = null;
		}

		public Object get(int key) {
			Node current = head;
			while (current != null && current.item.hashCode() != key)
				current = current.next;

			if (current != null && current.item.hashCode() == key)
				return current.item;

			return -1;
		}

		public void put(Node insert) {
			if (head == null) {
				head = insert;
				insert.next = null;
				return;
			}

			insert.next = head.next;
			head = insert;
		}

	}

	private class Node {
		Node next = null;
		Object item;

		public Node(Object item) {
			this.item = item;
		}
	}

	static private class ThreadID {
		private static volatile int nextID = 0;

		private static class ThreadLocalID extends ThreadLocal<Integer> {

			protected synchronized Integer initialValue() {
				return nextID++;
			}
		}

		private static ThreadLocalID threadID = new ThreadLocalID();

		public static int get() {
			return threadID.get();
		}

	}
}
